classdef ModelHierarchy < handle
%MODELHIERARCHY Simulink模型层次结构类
%   此类用于表示和管理Simulink模型的层次结构关系，特别是模型引用关系。
%   继承自handle类，支持引用语义和动态层次结构构建。
%
%属性:
%   mdlName - 模型名称（只读）
%   Children - 子模型层次结构对象数组（只读）
%
%方法:
%   ModelHierarchy - 构造函数
%   addChild - 添加子模型
%
%示例:
%   % 创建模型层次结构（非递归）
%   hierarchy = ModelHierarchy('TopModel', false);
%
%   % 创建模型层次结构（递归）
%   hierarchy = ModelHierarchy('TopModel', true);
%
%   % 访问模型信息
%   fprintf('顶层模型: %s\n', hierarchy.mdlName);
%   if ~isempty(hierarchy.Children)
%       fprintf('子模型数量: %d\n', length(hierarchy.Children));
%   end
%
%参见:
%   find_mdlrefs, handle
%

%版本: 1.0
%日期: 2024
%


    properties (SetAccess = private)
        mdlName    % 模型名称
        Children   % 子模型层次结构对象数组
    end

    methods
        function this = ModelHierarchy(mdlName, Recursion)
            %MODELHIERARCHY 构造函数
            %   创建模型层次结构对象，分析模型引用关系
            %
            %语法:
            %   obj = ModelHierarchy(mdlName, Recursion)
            %
            %输入参数:
            %   mdlName - 模型名称（字符串标量）
            %   Recursion - 是否递归分析（逻辑值）
            %
            %说明:
            %   - 使用find_mdlrefs查找模型引用
            %   - 当Recursion为true时，在基础工作空间设置hasParent标志
            %   - 自动排除自身引用，避免循环引用
            
            % 输入验证
            mustBeTextScalar(mdlName)
            mustBeNumericOrLogical(Recursion)
            
            this.mdlName = mdlName;
            
            % 查找模型引用 - 添加错误处理
            try
                refs = find_mdlrefs(mdlName);
                if numel(refs) > 1
                    for i = 1:numel(refs)
                        if ~strcmp(refs{i}, mdlName)
                            this.addChild(refs{i});
                        end
                    end
                end
            catch ME
                % 当find_mdlrefs失败时（如模型引用不在路径上），使用备用方法
                if contains(ME.message, 'must be on the MATLAB path') || ...
                   contains(ME.message, 'Unable to find referenced model')
                    fprintf('警告: 模型 %s 的某些引用不在MATLAB路径上，使用备用检测方法\n', mdlName);
                    this.detectModelReferencesManually(mdlName);
                else
                    % 其他未知错误，重新抛出
                    rethrow(ME);
                end
            end
            
            % 设置父模型标志（用于递归处理）
            if Recursion
                evalin('base', ['hasParent.', mdlName, ' = true;']);
            end
        end

        function addChild(this, refMdlName)
            %ADDCHILD 添加子模型到层次结构
            %   将指定的引用模型作为子模型添加到当前层次结构中
            %
            %语法:
            %   addChild(obj, refMdlName)
            %
            %输入参数:
            %   refMdlName - 引用模型名称（字符串）
            %
            %说明:
            %   - 递归创建子模型的ModelHierarchy对象
            %   - 自动处理多层嵌套的模型引用关系
            %   - 当子模型不在路径上时，创建特殊标记的节点
            
            try
                this.Children{end+1} = ModelHierarchy(refMdlName, true);
            catch ME
                % 如果子模型创建失败（如不在路径上），创建一个特殊的标记节点
                if contains(ME.message, 'must be on the MATLAB path') || ...
                   contains(ME.message, 'Unable to find referenced model') || ...
                   contains(ME.message, 'cannot be found')
                    % 创建一个特殊的ModelHierarchy对象来表示不在路径上的模型
                    notFoundChild = this.createNotOnPathChild(refMdlName);
                    this.Children{end+1} = notFoundChild;
                else
                    % 其他未知错误，重新抛出
                    rethrow(ME);
                end
            end
        end
        
        function detectModelReferencesManually(this, mdlName)
            %DETECTMODELREFERENCESMANUALLY 手动检测模型引用
            %   当find_mdlrefs失败时的备用方法，通过直接分析模型文件来查找引用
            %
            %语法:
            %   detectModelReferencesManually(obj, mdlName)
            %
            %输入参数:
            %   mdlName - 要分析的模型名称
            %
            %说明:
            %   - 尝试加载模型并查找ModelReference块
            %   - 对于找到的模型引用，尝试添加为子节点
            %   - 如果引用的模型不在路径上，添加特殊标记的节点
            
            % try
                % 尝试加载模型
                modelHandle = load_system(mdlName);
                
                % 查找所有ModelReference块
                modelRefBlocks = find_system(modelHandle, 'BlockType', 'ModelReference');
            
                
                if ~isempty(modelRefBlocks)
                    for i = 1:length(modelRefBlocks)
                            refModelName = get_param(modelRefBlocks(i), 'ModelName')
                            
                            if ~isempty(refModelName) && ~strcmp(refModelName, 'Model') && ...
                               ~strcmp(refModelName, mdlName) % 避免自引用
                                % 检查引用的模型是否在路径上
                                if this.isModelOnPath(refModelName)
                                    this.addChild(refModelName);
                                else
                                    % 创建特殊标记的节点表示不在路径上的模型
                                    notFoundChild = this.createNotOnPathChild(refModelName);
                                    this.Children{end+1} = notFoundChild;
                                end
                            end
                    end
                end
                
                % 关闭模型（如果是临时加载的）
                if ~bdIsLoaded(mdlName)
                    close_system(modelHandle, 0);
                end
                
            % catch ME
            %     fprintf('手动检测模型引用失败: %s - %s\n', mdlName, ME.message);
            % end
        end
        
        function isOnPath = isModelOnPath(~, modelName)
            %ISMODELON PATH 检查模型是否在MATLAB路径上
            %   检查指定模型文件是否可以在MATLAB路径上找到
            %
            %语法:
            %   isOnPath = isModelOnPath(obj, modelName)
            %
            %输入参数:
            %   modelName - 模型名称
            %
            %输出参数:
            %   isOnPath - 逻辑值，true表示模型在路径上
            
            isOnPath = false;
            extensions = {'.slx', '.mdl'};
            
            for ext = extensions
                fullName = [modelName, ext{1}];
                foundPath = which(fullName);
                if ~isempty(foundPath) && exist(foundPath, 'file')
                    isOnPath = true;
                    return;
                end
            end
        end
        
        function notFoundChild = createNotOnPathChild(~, refMdlName)
            %CREATENOTONPATHCHILD 创建表示"不在路径上"的特殊子节点
            %   为不在MATLAB路径上的引用模型创建特殊标记的ModelHierarchy对象
            %
            %语法:
            %   notFoundChild = createNotOnPathChild(obj, refMdlName)
            %
            %输入参数:
            %   refMdlName - 引用模型名称
            %
            %输出参数:
            %   notFoundChild - 特殊标记的ModelHierarchy对象
            
            % 创建一个简单的结构体来模拟ModelHierarchy对象的接口
            % 这样可以避免递归构造函数调用的问题
            notFoundChild = struct();
            notFoundChild.mdlName = [refMdlName, ' (not on path)'];
            notFoundChild.Children = {};
        end
    end
end